package com.gsoe.problem.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.gsoe.base.enumeration.ExamStatus;
import com.gsoe.base.enumeration.ProblemType;
import com.gsoe.base.exception.ProblemException;
import com.gsoe.base.model.PageResult;
import com.gsoe.base.model.Result;
import com.gsoe.base.model.User;
import com.gsoe.base.utils.UserUtil;
import com.gsoe.problem.client.JudgeClient;
import com.gsoe.problem.dto.CreatExamByRandomDTO;
import com.gsoe.problem.dto.ExamDTO;
import com.gsoe.problem.dto.SaveExamDTO;
import com.gsoe.problem.dto.TeacherCommentDTO;
import com.gsoe.problem.mapper.ExamMapper;
import com.gsoe.problem.mapper.ExamProblemMapper;
import com.gsoe.problem.po.*;
import com.gsoe.problem.service.ExamProblemService;
import com.gsoe.problem.service.ExamRegisterUserService;
import com.gsoe.problem.service.ExamService;
import com.gsoe.problem.service.ProblemService;
import com.gsoe.problem.utils.BloomFilterUtil;
import com.gsoe.problem.vo.ExamAdminVO;
import com.gsoe.problem.vo.ExamVO;
import com.gsoe.problem.vo.NeedPeopleProblemVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 考试表 服务实现类
 * </p>
 *
 * @author xiaoma
 * @since 2024-10-27
 */
@Service
@Slf4j
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {
    @Autowired
    private ExamRegisterUserService examRegisterUserService;
    @Autowired
    private ExamProblemService examProblemService;
    @Autowired
    private JudgeClient judgeClient;
    @Autowired
    private ExamProblemMapper examProblemMapper;
    @Autowired
    private ExamMapper examMapper;
    @Autowired
    private ProblemService problemService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private BloomFilterUtil bloomFilterUtil;

    @Override
    public PageResult<ExamVO> getExamList(ExamDTO dto) {
        Page<Exam> page=new Page<>(dto.getPageNo(),dto.getPageSize());
        page = lambdaQuery()
                .like(dto.getExamName() != null, Exam::getExamName, dto.getExamName())
                .eq(dto.getId() != null, Exam::getId, dto.getId())
                .orderByDesc(Exam::getEndTime)
                .page(page);

        List<Long> classIdList = page.getRecords().stream().map(Exam::getClassBelong).toList();
        List<Long> examIdList = page.getRecords().stream().map(Exam::getId).toList();
        if(CollectionUtil.isEmpty(examIdList))return PageResult.of(page,p->new ExamVO());
        User loginUser = UserUtil.getLoginUser();
        //考试id 已注册的信息,一次性把数据全拿过来 java内部处理，减少mysql压力
        Map<Long, List<ExamRegisterUser>> collect = examRegisterUserService.lambdaQuery()
                .in(ExamRegisterUser::getExamId, examIdList)
                .list().stream().collect(Collectors.groupingBy(ExamRegisterUser::getExamId));


        //TODO 找班级名
        PageResult<ExamVO> examVOPageResult = PageResult.of(page, exam -> {
            ExamVO examVO = new ExamVO();
            BeanUtils.copyProperties(exam, examVO);
            //当前测验已注册的学生信息
            List<ExamRegisterUser> examRegisterUsers = collect.get(exam.getId());
            if (examRegisterUsers == null) {
                examRegisterUsers = new ArrayList<>();
            }
            int ranking = examRegisterUsers.size();
            examVO.setPeopleNum(ranking);
            if (exam.getEndTime().isBefore(LocalDateTime.now())) {
                examVO.setStatus(5); //已结束

                examVO.setAllScore(exam.getScore());
                //当前用户注册信息
                ExamRegisterUser examRegisterUser = null;
                //找到登录用户信息
                for (ExamRegisterUser registerUser : examRegisterUsers) {
                    if (registerUser.getUserId().equals(loginUser.getId())) {
                        examRegisterUser = registerUser;
                        break;
                    }
                }
                //如果注册了，查找排名
                if (examRegisterUser != null) {
                    examVO.setGetScore(examRegisterUser.getScore());
                    for (ExamRegisterUser registerUser : examRegisterUsers) {
                        if (registerUser.getScore() < examRegisterUser.getScore()) {
                            ranking--;
                        }
                    }
                    examVO.setRanking(ranking);
                }
            } else if (exam.getBeginTime().isBefore(LocalDateTime.now())) {
                examVO.setStatus(3); //已开始未报名
                for (ExamRegisterUser registerUser : examRegisterUsers) {
                    if (registerUser.getUserId().equals(loginUser.getId())) {
                        examVO.setStatus(4); //已开始已报名
                        break;
                    }
                }

            } else {
                examVO.setStatus(1); //未报名
                for (ExamRegisterUser registerUser : examRegisterUsers) {
                    if (registerUser.getUserId().equals(loginUser.getId())) {
                        examVO.setStatus(2); //已报名
                        break;
                    }
                }
            }
            return examVO;
        });

        List<ExamVO> items = examVOPageResult.getItems();
        items.sort((e1,e2)->{
            if(!Objects.equals(e1.getStatus(), e2.getStatus())){return e1.getStatus()- e2.getStatus();}
            else return e1.getBeginTime().compareTo(e2.getBeginTime());
        });
        examVOPageResult.setItems(items);
        return examVOPageResult;
    }

    @Override
    public PageResult<ExamAdminVO> getExamAdminList(ExamDTO dto) {
        Page<Exam> page=new Page<>(dto.getPageNo(),dto.getPageSize());
        page = lambdaQuery()
                .like(dto.getExamName() != null, Exam::getExamName, dto.getExamName())
                .eq(dto.getId() != null, Exam::getId, dto.getId())
                .orderByDesc(Exam::getEndTime)
                .page(page);

        return PageResult.of(page,p->{
            ExamAdminVO examAdminVO=new ExamAdminVO();
            BeanUtils.copyProperties(p,examAdminVO);
            return examAdminVO;
        });
    }

    @Override
    public void saveExam(SaveExamDTO saveExamDTO) {
        LocalDateTime beginTime = saveExamDTO.getBeginTime();
        if(beginTime==null || beginTime.isAfter(LocalDateTime.now().plusDays(364))){
            throw new ProblemException("saveExam:请正确设置开始时间");
        }


        Exam exam=new Exam();
        if(saveExamDTO.getId()!=null){
            examProblemService.remove(new QueryWrapper<ExamProblem>().eq("exam_id", saveExamDTO.getId()));
            exam.setUpdateTime(LocalDateTime.now());
            exam.setUpdateUserName(UserUtil.getLoginUser().getUserName());
        }

        BeanUtils.copyProperties(saveExamDTO,exam);
        if(exam.getBeginTime()==null)throw new ProblemException("开始时间不能为空");
        if(exam.getScore() ==null) throw new ProblemException("总分不能为空");
        exam.setEndTime(saveExamDTO.getBeginTime().plusMinutes(saveExamDTO.getValidity()));
        saveOrUpdate(exam);

        List<Integer> examScoreList = saveExamDTO.getExamScoreList();
        List<List<Problem>> examProblemList = saveExamDTO.getExamProblemList();
        Map<Long, Boolean> needPeople = saveExamDTO.getNeedPeople();
        for (int i = 1; i < examProblemList.size(); i++) {
            List<Problem> problemList = examProblemList.get(i);
            int finalI = i;
            List<ExamProblem> list = problemList.stream().map(p -> {
                ExamProblem examProblem = new ExamProblem();
                examProblem.setExamId(exam.getId());
                examProblem.setProblemId(p.getId());
                examProblem.setScore(examScoreList.get(finalI));
                examProblem.setNeedPeople(needPeople.get(p.getId()) != null && needPeople.get(p.getId()));
                return examProblem;
            }).toList();

            examProblemService.saveBatch(list);
        }
        if(saveExamDTO.getId()==null)bloomFilterUtil.addToBloomFilter(String.valueOf(exam.getId()));

        RLock rLock = redissonClient.getReadWriteLock("examProblemLock:" + saveExamDTO.getId()).writeLock();
        try {
            rLock.lock();
            stringRedisTemplate.delete("examProblem:"+saveExamDTO.getId());
        }catch (Exception e){
            log.error("saveExam:"+e.getMessage());
        }finally {
            rLock.unlock();
        }
    }

    @Override
    @Transactional
    public void delExam(Long id) {
        removeById(id);
        examProblemService.remove(new QueryWrapper<ExamProblem>().eq("exam_id", id));
        examRegisterUserService.remove(new QueryWrapper<ExamRegisterUser>().eq("exam_id", id));
        Result<String> result = judgeClient.delByTextId(id);
        if(result.getCode()!=200)throw new ProblemException("delExam:删除缓存失败:"+result.getMsg());
        stringRedisTemplate.delete("examProblem:"+id);
    }

    @Override
    public SaveExamDTO getExamById(Long id) {
        Exam exam = getById(id);
        if(exam==null)throw new ProblemException("getExamById:无此id");
        SaveExamDTO saveExamDTO=new SaveExamDTO();
        BeanUtils.copyProperties(exam,saveExamDTO);

        List<Problem> problemListByExamId = examProblemMapper.getProblemListByExamId(id,null);
        Map<Integer, List<Problem>> collected = problemListByExamId.stream().collect(Collectors.groupingBy(Problem::getType));

        List<List<Problem>>examProblemList=new ArrayList<>();
        List<Integer> examScoreList=new ArrayList<>();
        Map<Long, Boolean> needPeople=new HashMap<>();
        for (int i = 0; i < 7; i++) {
            List<Problem> problemListByCollected = collected.get(i);
            if(CollectionUtil.isEmpty(problemListByCollected)){
                examScoreList.add(i,1);
                examProblemList.add(i,new ArrayList<>());
                continue;
            }
            examProblemList.add(i,problemListByCollected);

            Problem problem = collected.get(i).get(0);
            ExamProblem examProblem = examProblemService.lambdaQuery()
                    .eq(ExamProblem::getExamId, id)
                    .eq(ExamProblem::getProblemId,problem.getId())
                    .one();
            examScoreList.add(i,examProblem.getScore());

            if(i==5){
                List<Problem> problemList = collected.get(5);
                if(CollectionUtil.isNotEmpty(problemList)){
                    List<Long> list = problemList.stream().map(Problem::getId).toList();
                    List<ExamProblem> examProblems = examProblemService.lambdaQuery()
                            .eq(ExamProblem::getExamId, id)
                            .in(ExamProblem::getProblemId, list)
                            .list();
                    examProblems.forEach(e -> {
                        needPeople.put(e.getProblemId(),e.getNeedPeople());
                    });
                }

            }


        }
        saveExamDTO.setExamProblemList(examProblemList);
        saveExamDTO.setExamScoreList(examScoreList);
        saveExamDTO.setNeedPeople(needPeople);
        return saveExamDTO;
    }

    @Override
    @Transactional
    public Long creatExamByRandom(CreatExamByRandomDTO creatExamByRandomDTO) {
        LocalDateTime beginTime = creatExamByRandomDTO.getBeginTime();
        if(beginTime==null || beginTime.isAfter(LocalDateTime.now().plusDays(364))){
            throw new ProblemException("saveExam:请正确设置开始时间");
        }
        Exam exam=new Exam();
        BeanUtils.copyProperties(creatExamByRandomDTO,exam);
        exam.setEndTime(exam.getBeginTime().plusMinutes(creatExamByRandomDTO.getValidity()));
        exam.setCreateTime(LocalDateTime.now());
        Integer i = examMapper.insertWithLock(exam);
        if(i<=0)throw new ProblemException("creatExamByRandom:考试名已存在，或重复创建");

        RandProblem randProblem=new RandProblem();
        List<Integer> examNumberList = creatExamByRandomDTO.getExamNumberList();
        List<Integer> examScoreList = creatExamByRandomDTO.getExamScoreList();

        if(CollectionUtil.isEmpty(examNumberList)){return exam.getId();}
        if(CollectionUtil.isEmpty(examScoreList) || examScoreList.size()<6){throw  new ProblemException("creatExamByRandom:参数错误");}
        randProblem.setSingleChooseNum(examNumberList.get(ProblemType.SINGLE));
        randProblem.setBigNum(examNumberList.get(ProblemType.BIG));
        randProblem.setFillNum(examNumberList.get(ProblemType.FILL));
        randProblem.setJudgeNum(examNumberList.get(ProblemType.JUDGE));
        randProblem.setMultipleChooseNum(examNumberList.get(ProblemType.MULTIPLE));
        List<Long> randProblemList = problemService.createRandProblemList(randProblem);
        if(CollectionUtil.isEmpty(randProblemList)){return exam.getId();}
        List<Problem> problemList = problemService.lambdaQuery()
                .in(Problem::getId, randProblemList)
                .list();
        if(CollectionUtil.isEmpty(randProblemList)){return exam.getId();}
        List<ExamProblem> examProblemList = problemList.stream().map(p -> {
            ExamProblem examProblem = new ExamProblem();
            examProblem.setExamId(exam.getId());
            examProblem.setProblemId(p.getId());
            examProblem.setScore(examScoreList.get(p.getType()));
            return examProblem;
        }).toList();


        RLock rLock = redissonClient.getReadWriteLock("examProblemLock:" + exam.getId()).writeLock();
        try {
            rLock.lock();
            examProblemService.saveBatch(examProblemList);
            stringRedisTemplate.delete("examProblem:"+exam.getId());
            bloomFilterUtil.addToBloomFilter(String.valueOf(exam.getId()));
        }catch (Exception e){
            log.error("creatExamByRandom:"+e.getMessage());
            throw  new RuntimeException(e.getMessage());
        }finally {
            rLock.unlock();
        }
        return exam.getId();
    }

    @Override
    public Long getExamRemainingTime(Long examId,Long userId) {
        if(examId==null || userId==null){throw new ProblemException("getExamRemainingTime:数据缺失");}
        ExamRegisterUser examRegisterUser = examRegisterUserService.lambdaQuery()
                .eq(ExamRegisterUser::getExamId, examId)
                .eq(ExamRegisterUser::getUserId, userId)
                .one();

        if(!examRegisterUser.getStatus().equals(ExamStatus.JOIN)){
            return -1L;
        }

        Exam exam = getById(examId);
        return Duration.between(LocalDateTime.now(),exam.getEndTime()).getSeconds();
    }

    @Override
    public List<NeedPeopleProblemVO> getNeedProblemList(Long examId) {
        if(examId==null)throw new ProblemException("getNeedProblemList:id不能为空");
        return examMapper.getNeedProblemList(examId);
    }
}
